﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.IO;
using System.Configuration;
using ICSharpCode.SharpZipLib;
using ICSharpCode.SharpZipLib.Zip;
using System.Diagnostics;
using System.Reflection;
using ChromiumUpdater.Data;

namespace ChromiumUpdater
{
    public partial class MainForm : Form
    {
        private bool proxyEnabled = false;
        private WebClient webClient = new WebClient();
        private string latest = "1";
        private ConfigSettings config = new ConfigSettings();
        private string executingAssebmlyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
        private string chromiumPath = @"C:\Chromium\chrome.exe";
        private bool forceQuit = false;
        

        public MainForm()
        {
            InitializeComponent();
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            //Load the config and throw and error if any setting could not be loaded
            if ((config = ConfigManager.LoadConfig()) == null)
            {
                UpdateStatus("Status: Error",
                    "An error occurred whilst loading config settings. Please check that all config settings are correct in config file",
                    1, true, true);
                forceQuit = true;
                this.Close();
            }

            TitleBar.Text = "Chromium Updater v" + Assembly.GetExecutingAssembly().GetName().Version;
            
            int x = Screen.PrimaryScreen.WorkingArea.Width - this.Width;
            int y = Screen.PrimaryScreen.WorkingArea.Height - this.Height;
            this.Location = new Point(x, y);

            PopulateForm();
            //SetUpWebClient();

            //Create downloaded files folder:
            if (!Directory.Exists(executingAssebmlyPath + @"\DownloadedFiles"))
                System.IO.Directory.CreateDirectory(executingAssebmlyPath + @"\DownloadedFiles");

            //Create log folder:
            if (!Directory.Exists(executingAssebmlyPath + @"\Logs"))
                System.IO.Directory.CreateDirectory(executingAssebmlyPath + @"\Logs");
        }

        private void PopulateForm()
        {
            //Proxy settings section
            proxyEnabled = config.proxyEnabled;
            enableProxyCheckbox.Checked = config.proxyEnabled;
            proxyUrlTextBox.Enabled = enableProxyCheckbox.Checked;
            proxyPortTextBox.Enabled = enableProxyCheckbox.Checked;
            proxyUrlTextBox.Text = config.proxyURL;
            proxyPortTextBox.Text = config.proxyPort;
            updateTimer.Interval = (int)hoursUpDown.Value * 1000 * 60 * 60;

            //Auto update section
            switch (config.autoUpdateMode)
            {
                case 1: installOption1Radio.Checked = true; break;
                case 2: installOption2Radio.Checked = true; break;
                case 3: installOption3Radio.Checked = true; break;
                default: installOption3Radio.Checked = true; break;             
            }
            hoursUpDown.Value = config.autoUpdateHours;

            //Chromium path section
            chromiumPath = config.chromePath;
            chromeFolderTextbox.Text = chromiumPath;

            statusLbl.Text = "Status: Idle";

        }

        private void SetUpWebClient()
        {
            webClient.Headers.Add(HttpRequestHeader.UserAgent, "ChromiumUpdater Header");
            
            //TODO: possibly add regex her to determine if the proxy is in correct format. Could be difficult
            //if dns is allowed
            if (config.proxyEnabled)
            {
                if (string.IsNullOrEmpty(proxyUrlTextBox.Text) || string.IsNullOrEmpty(proxyPortTextBox.Text))
                {
                    UpdateStatus("Status: Warning", "No proxy address specified. Please specify a proxy address or disable proxy", 2, true, true);
                    return;
                }
                else
                    webClient.Proxy = WebClientHelper.ProxyString(proxyUrlTextBox.Text, proxyPortTextBox.Text);
            }
            else
            {
                webClient.Proxy = null;
            }
        }

        public bool SetLatestVersion()
        {
            SetUpWebClient();

            try
            {
                //Download Chromium LATEST file
                statusLbl.Text = "Status: Checking For Updates";
                webClient.DownloadFile(new Uri("http://build.chromium.org/f/chromium/snapshots/Win_Webkit_Latest/LATEST"), executingAssebmlyPath + @"\DownloadedFiles\latest.txt");

                //Read Latest Version to string
                StreamReader streamReader = new StreamReader(executingAssebmlyPath + @"\DownloadedFiles\latest.txt");
                latest = streamReader.ReadToEnd();
                streamReader.Close();
                return true;
            }
            catch (Exception ex)
            {
                UpdateTextBoxConfig();
                Logger.Log("SetLatestVersion() Failed", config, ex);

                return false;
            }
        }
        
        private int UpdateRequired()
        {
            if (!SetLatestVersion())
            {              
                return -1;
            }

            //Compare versions and inform user as appropriate.
            if (Convert.ToInt32(config.currentVersion) < Convert.ToInt32(latest))
            {
                return Convert.ToInt32(latest);
            }
            else
            {
                return 0;
            }
        }
        
        /// <summary>
        /// Checks for updates
        /// </summary>
        private void CheckForUpdates()
        {
            int updateRequired = UpdateRequired();
            
            if (updateRequired > 0)
            {
                statusLbl.Text = "Status: Updates Available";
                    DialogResult result = MessageBox.Show(string.Format("The latest Chromium build is {0}. You are currently using build {1}. Would you like to update now?",
                        latest, config.currentVersion), "New Version Available", MessageBoxButtons.YesNo, MessageBoxIcon.Information);

                    if (result == System.Windows.Forms.DialogResult.Yes)
                    {
                        DownloadUpdate(latest);
                    }

            }
            else if (updateRequired == 0)
            {
                UpdateStatus("Status: Latest Version Installed",
                (string.Format("You currently have the latest Chromium Build ({0}). Congratulations, No further action is required",
                latest)), 0, true, true);
            }
            else
            {
                UpdateStatus("Status: Error", "Latest version could not be determined. Ensure all settings are correct", 1, true, true);
            }
        }


        /// <summary>
        /// Checks for updates
        /// </summary>
        private void InstallLatestVersion()
        {
            int updateRequired = UpdateRequired();

            if (updateRequired > 0)
            {
                    DownloadUpdate(latest);
            }
            else if (updateRequired == 0)
            {
                UpdateStatus("Status: Latest Version Installed",
                (string.Format("You currently have the latest Chromium Build ({0}). Congratulations, No further action is required",
                latest)), 0, true, true);
            }
            else
            {
                UpdateStatus("Status: Error", "Latest version could not be determined. Ensure all settings are correct", 1, true, true);
            }
        }

        
        private void DownloadUpdate(string latest)
        {
            //Create downloaded files folder:
            if (Directory.Exists(executingAssebmlyPath + @"\DownloadedFiles"))
            {
                Directory.Delete(executingAssebmlyPath + @"\DownloadedFiles", true);
                System.IO.Directory.CreateDirectory(executingAssebmlyPath + @"\DownloadedFiles");
            }
            
            SetUpWebClient();           
            try
            {
                webClient.DownloadFileCompleted += new AsyncCompletedEventHandler(Completed);
                webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(ProgressChanged);
                progressBar.Style = ProgressBarStyle.Blocks;
                progressBar.Visible = true;
                //statusLbl.Text = "Status: Downloading Latest Version";
                UpdateStatus("Status: Downloading Update", string.Format("Updater is downloading the latest version of Chromium ({0})", latest), 1, true, true); 
                webClient.DownloadFileAsync(new Uri("http://build.chromium.org/f/chromium/snapshots/Win_Webkit_Latest/"+latest+"/chrome-win32.zip"), executingAssebmlyPath + @"\DownloadedFiles\\chrome-win32.zip");
            }
            catch(Exception ex)
            {
                UpdateTextBoxConfig();
                Logger.Log("DownloadUpdate() - Failed", config, ex);
                
                UpdateStatus("Status: Connection error", "Could not connect to server. Please check your proxy settings are correct and that no firewalls or Anti-Virus software are blocking the operation of this program", 1, true, true);              
                return;
            }

        }

        
        private void AutoUpdate()
        {
            SetUpWebClient();

            //Download Chromium LATEST file
            try
            {
                statusLbl.Text = "Status: Checking For Updates";
                webClient.DownloadFile(new Uri("http://build.chromium.org/f/chromium/snapshots/Win_Webkit_Latest/LATEST"), executingAssebmlyPath + @"\DownloadedFiles\latest.txt");
            }
            catch (Exception ex)
            {
                UpdateTextBoxConfig();
                Logger.Log("AutoUpdate - Failed", config, ex);
                
                UpdateStatus("Status: Connection error", "Could not connect to server. Please check your proxy settings are correct and that no firewalls or Anti-Virus software are blocking the operation of this program", 1, true, true); 
                return;
            }

            //Read Latest Version to string
            StreamReader streamReader = new StreamReader(executingAssebmlyPath + @"\DownloadedFiles\latest.txt");
            latest = streamReader.ReadToEnd();
            streamReader.Close();

            //Get current version from config file
            string current = config.currentVersion;

            if (Convert.ToInt32(current) < Convert.ToInt32(latest))
            {
                DownloadUpdate(latest);
            }
        }
        
        private void InstallUpdate()
        {
            CloseChromiumProcesses();                      
        }

        /// <summary>
        /// Update the status in all appropriate areas
        /// </summary>
        /// <param name="message">The message to display</param>
        /// <param name="icon">Icon to display: 0=Info, 1=Error, 2=Warning</param>
        private void UpdateStatus(string status, string message, int icon, bool showMessageBoxIfMaximised, bool fade)
        {
            statusLbl.Text = status;

            StatusDialog stat = new StatusDialog(fade, message, status, 15000);
            stat.Show();

            if (this.WindowState == FormWindowState.Minimized)
            {
                //switch (icon)
                //{
                //    case 0: notifyIcon.BalloonTipIcon = ToolTipIcon.Info; notifyIcon.BalloonTipTitle = status; break;
                //    case 1: notifyIcon.BalloonTipIcon = ToolTipIcon.Error; notifyIcon.BalloonTipTitle = status; break;
                //    case 2: notifyIcon.BalloonTipIcon = ToolTipIcon.Warning; notifyIcon.BalloonTipTitle = status; break;
                //}
                //notifyIcon.BalloonTipText = message;
                //notifyIcon.ShowBalloonTip(5000);

                
            }

            else
            {
                if (showMessageBoxIfMaximised)
                {
                    switch (icon)
                    {
                        case 0: MessageBox.Show(message, status, MessageBoxButtons.OK, MessageBoxIcon.Information); break;
                        case 1: MessageBox.Show(message, status, MessageBoxButtons.OK, MessageBoxIcon.Error); break;
                        case 2: MessageBox.Show(message, status, MessageBoxButtons.OK, MessageBoxIcon.Warning); break;
                    }
                }
            }
        }

        private void UnzipWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            UnzipFile(executingAssebmlyPath + @"\DownloadedFiles\chrome-win32.zip", executingAssebmlyPath + @"\DownloadedFiles\");
        }

        private void UnzipWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            BackgroundWorker CopyFilesWorker = new BackgroundWorker();
            CopyFilesWorker.DoWork +=
            new DoWorkEventHandler(CopyFilesWorker_DoWork);
            CopyFilesWorker.RunWorkerCompleted +=
                new RunWorkerCompletedEventHandler(CopyFilesWorker_RunWorkerCompleted);

            if (CopyFilesWorker.IsBusy != true)
            {
                CopyFilesWorker.RunWorkerAsync();
            }
            
           
        }

        private void CopyFilesWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                //create installation folder if it doesn't exist        
                int startIndex = chromiumPath.LastIndexOf("\\") + 1;

                string folderPath = chromiumPath.Substring(0, startIndex);
                if (!Directory.Exists(folderPath))
                    System.IO.Directory.CreateDirectory(folderPath);
                if (!Directory.Exists(folderPath + @"\locales"))
                    System.IO.Directory.CreateDirectory(folderPath + @"\locales");

                //Copy files to chrome directory
                string sourcePath = executingAssebmlyPath + @"\DownloadedFiles\chrome-win32\";
                string destinationPath = chromiumPath.Substring(0, startIndex);

                foreach (string newPath in Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories))
                    File.Copy(newPath, newPath.Replace(sourcePath, destinationPath), true);
            }
            catch (Exception ex)
            {
                UpdateTextBoxConfig();
                Logger.Log("CopyFilesWorker_DoWork() - Failed", config, ex);
                
                UpdateStatus("Status: Error", "An error occured whilst copying files", 1, true, false); 
                return;
            }

            //delete temporary files
            //executingAssebmlyPath + @"\DownloadedFiles\";
            System.IO.DirectoryInfo directory = new DirectoryInfo(executingAssebmlyPath + @"\DownloadedFiles");

            foreach (FileInfo file in directory.GetFiles())
                file.Delete();
            foreach (DirectoryInfo dir in directory.GetDirectories())
                dir.Delete(true);
        }

        private void CopyFilesWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            progressBar.Visible = false;
            UpdateStatus("Status: Latest Version Installed", "The latest version of Chromium has been installed", 0, false, false);

            config.currentVersion = latest;
 
            ConfigManager.SaveConfig(config);
            
            StartChromium();
            Application.Restart();
        }

        private void StartChromium()
        {
            Process proc = new Process();
            proc.StartInfo.FileName = chromiumPath;
            proc.StartInfo.Arguments = @"--restore-last-session";
            proc.Start();
        }

        private bool UnzipFile(string zipFilePath, string targetFolder)
        {
            try
            {
                ZipInputStream zipInputStream = new ZipInputStream(System.IO.File.OpenRead(zipFilePath));
                ZipEntry theEntry;

                while ((theEntry = zipInputStream.GetNextEntry()) != null)
                {
                    string directoryName = Path.Combine(targetFolder, Path.GetDirectoryName(theEntry.Name));
                    string fileName = Path.GetFileName(theEntry.Name);

                    if (!Directory.Exists(directoryName))
                    {
                        Directory.CreateDirectory(directoryName);
                    }

                    if (fileName != String.Empty)
                    {
                        FileStream streamWriter = System.IO.File.Create(Path.Combine(directoryName, fileName));
                        int size = 2048;
                        byte[] data = new byte[2048];
                        while (true)
                        {
                            size = zipInputStream.Read(data, 0, data.Length);
                            if (size > 0)
                            {
                                streamWriter.Write(data, 0, size);
                            }
                            else
                            {
                                break;
                            }
                        }
                        streamWriter.Close();
                    }
                }
                zipInputStream.Close();

                return true;
            }
            catch (Exception ex)
            {
                UpdateTextBoxConfig();
                Logger.Log("UnzipFile() - Failed", config, ex);
                
                UpdateStatus("Status: Error", "An Error Occurred. Please ensure all instances of Chromium are currently closed before running this updater", 1, true, false); 
                return false;
            }
        }

        private void CloseChromiumProcesses()
        {
            Process[] processes;
            string procName = "chrome";       
            processes = Process.GetProcessesByName(procName);

            try
            {
                foreach (Process proc in processes)
                {
                    try
                    {
                        proc.Kill();
                        proc.WaitForExit();
                    }
                    catch (InvalidOperationException ex)
                    {
                        UpdateTextBoxConfig();
                        Logger.Log("CloseChromiumProcesses() Failed", config, ex);
                    }
                }
            }
            catch (System.NullReferenceException ex)
            {
                UpdateTextBoxConfig();
                Logger.Log("CloseChromiumProcesses() Failed", config, ex);
            }
            catch (Exception ex)
            {
                UpdateTextBoxConfig();
                Logger.Log("CloseChromiumProcesses() Failed", config, ex);
            }


            BackgroundWorker UnzipWorker = new BackgroundWorker();
            UnzipWorker.DoWork +=
            new DoWorkEventHandler(UnzipWorker_DoWork);
            UnzipWorker.RunWorkerCompleted +=
                new RunWorkerCompletedEventHandler(UnzipWorker_RunWorkerCompleted);

            if (UnzipWorker.IsBusy != true)
            {
                UnzipWorker.RunWorkerAsync();
            }

            progressBar.Visible = true;
            progressBar.Style = ProgressBarStyle.Marquee;
            UpdateStatus("Status: Installing Latest Version", "Currently installing latest version of Chromium", 0, false, false); 
        }
   
        #region eventhandlers
        

        private void frmMain_Resize(object sender, EventArgs e)
        {
            if (FormWindowState.Minimized == this.WindowState)
            {
                StatusDialog stat = new StatusDialog(false, "This is my awesome message", "Test", 5000);
                this.Hide();
                notifyIcon.Visible = true;
                //notifyIcon.ShowBalloonTip(500);  

                stat.Show();
            }
            else if (FormWindowState.Normal == this.WindowState)
            {
                notifyIcon.Visible = false;
            }

            //notifyIcon.BalloonTipText = "My application still working...";
            //notifyIcon.BalloonTipTitle = "My Sample Application";
            //notifyIcon.BalloonTipIcon = ToolTipIcon.Info;
        }
        
        private void installLatestButton_Click(object sender, EventArgs e)
        {
            InstallLatestVersion();
        }

        private void ProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            progressBar.Value = e.ProgressPercentage;
        }

        private void updateButton_Click(object sender, EventArgs e)
        {
            CheckForUpdates();
        }

        private void enableProxyCheckbox_CheckedChanged(object sender, EventArgs e)
        {
            proxyEnabled = enableProxyCheckbox.Checked;
            proxyUrlTextBox.Enabled = enableProxyCheckbox.Checked;
            proxyPortTextBox.Enabled = enableProxyCheckbox.Checked;

            config.proxyEnabled = proxyEnabled;
        }

        private void Completed(object sender, AsyncCompletedEventArgs e)
        {
            //MessageBox.Show("Download completed!");
            UpdateStatus("Status: Download Complete", "Latest version of Chromium downloaded", 0, false, false);
            progressBar.Visible = false;

            InstallUpdate();
        }

        private void chromeFolderBtn_Click(object sender, EventArgs e)
        {
            OpenFileDialog open = new OpenFileDialog();

            if (open.ShowDialog() == DialogResult.OK)
            {
                chromiumPath = open.FileName;
                chromeFolderTextbox.Text = chromiumPath;

                config.chromePath = chromiumPath;
            }
        }

        private void installOption1Radio_CheckedChanged(object sender, EventArgs e)
        {
            hoursUpDown.Enabled = true;
            config.autoUpdateMode = 1;
        }

        private void installOption2Radio_CheckedChanged(object sender, EventArgs e)
        {
            hoursUpDown.Enabled = true;
            config.autoUpdateMode = 2;
        }

        private void installOption3Radio_CheckedChanged(object sender, EventArgs e)
        {
            hoursUpDown.Enabled = false;
            config.autoUpdateMode = 3;
        }
        #endregion

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (!forceQuit)
            {
                UpdateTextBoxConfig();

                if (!ConfigManager.SaveConfig(config))
                    UpdateStatus("Status: Error", "An error occured and config was not saved correctly.", 1, true, false);
            }
        }

        public void UpdateTextBoxConfig()
        {
            //Update config settings for text boxes. All others updated whenever they change
            config.chromePath = chromeFolderTextbox.Text;
            config.proxyPort = proxyPortTextBox.Text;
            config.proxyURL = proxyUrlTextBox.Text;
        }


        private void notifyIcon_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            this.Show();
            this.WindowState = FormWindowState.Normal;
            int x = Screen.PrimaryScreen.WorkingArea.Width - this.Width;
            int y = Screen.PrimaryScreen.WorkingArea.Height - this.Height;
            this.Location = new Point(x, y);
        }

        private void updateTimer_Tick(object sender, EventArgs e)
        {
            if (installOption1Radio.Checked)
            {
                AutoUpdate();
            }

            if (installOption2Radio.Checked)
            {
                //TODO: Check for an update
                if(UpdateRequired() != 0)
                    UpdateStatus("Status: Chromium Update Available", "A Chromium update is available. Please click 'Install Latest Version' in the main window to install", 0, true, true);
            }

            if (installOption3Radio.Checked)
            {
                //Don't do anything
            }


        }

        private void hoursUpDown_ValueChanged(object sender, EventArgs e)
        {
            updateTimer.Interval = (int)hoursUpDown.Value * 1000 * 60 *60;
            config.autoUpdateHours = (int)hoursUpDown.Value;
        }


        private void pictureBox1_Click(object sender, EventArgs e)
        {
            this.WindowState = FormWindowState.Minimized;
        }


    }
}
